﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// Simple Service Administration Tool for WinNT based systems.
// Copyright © 2010 - Silvan Gehrig
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
// 
// Author:
//  Silvan Gehrig
//
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections;
using System.Collections.Generic;
using SSATool.Common.Util;
using SSATool.Common.Util.WeakEvents;
using SSATool.DL.Common;
using SSATool.DL.Common.Dto;

#endregion

namespace SSATool.BL.DM
{
    /// <summary>
    /// Represents a collection of service instances.
    /// </summary>
    public class ServiceCollection : ICollection<Service>
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private readonly FastSmartWeakEvent<EventHandler<ServiceCollectionChangedEventArgs>> _onCollectionChanged = new FastSmartWeakEvent<EventHandler<ServiceCollectionChangedEventArgs>>();
        private readonly ServiceDtoConverter _converter;
        private readonly DomainContext _parent;
        private IDictionary<string, Service> _services;

        private bool _eventAdded;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Gets the number of objects contained in a specific ICollection collection.
        /// </summary>
        public int Count
        {
            get { return _services.Count; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is read only.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is read only; otherwise, <c>false</c>.
        /// </value>
        public bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// Gets the service with the given name.
        /// </summary>
        /// <param name="serviceName">Name of the service to retrieve.</param>
        /// <returns>Returns the retrieved service or fires an exception if it could not be found.</returns>
        public Service this[string serviceName]
        {
            get { return _services[serviceName]; }
        }

        internal DomainContext Context
        {
            get { return _parent; }
        }

        internal ServiceDtoConverter Converter
        {
            get { return _converter; }
        }

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceCollection"/> class.
        /// </summary>
        /// <param name="parent">Specifies the parent context instance.</param>
        public ServiceCollection(DomainContext parent)
        {
            PreCondition.AssertNotNull(parent, "parent");

            _converter = new ServiceDtoConverter(this);
            _parent = parent;
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------


        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<Service> GetEnumerator()
        {
            return _services.Values.GetEnumerator();
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable<Service>)this).GetEnumerator();
        }

        /// <summary>
        /// Adds the specified item.
        /// </summary>
        /// <param name="item">The item to add.</param>
        public void Add(Service item)
        {
            PreCondition.AssertNotNull(item, "item");
            _services[item.ServiceName] = item;
        }

        /// <summary>
        /// Removes all items from the collcetion.
        /// </summary>
        public void Clear()
        {
            _services.Clear();
        }

        /// <summary>
        /// Determines whether the current collection contains the specified item.
        /// </summary>
        /// <param name="item">The item to check.</param>
        /// <returns>
        /// 	<c>true</c> if the current collection contains the specified item; otherwise, <c>false</c>.
        /// </returns>
        public bool Contains(Service item)
        {
            PreCondition.AssertNotNull(item, "item");
            return _services.ContainsKey(item.ServiceName);
        }

        /// <summary>
        /// Determines whether the current collection contains the specified item.
        /// </summary>
        /// <param name="serviceName">The name of the service to check.</param>
        /// <returns>
        /// 	<c>true</c> if the current collection contains the specified item; otherwise, <c>false</c>.
        /// </returns>
        public bool Contains(string serviceName)
        {
            PreCondition.AssertNotNull(serviceName, "serviceName");
            return _services.ContainsKey(serviceName);
        }

        /// <summary>
        /// Copies the elements of the ICollection to an Array, starting at a particular Array index.
        /// </summary>
        /// <param name="array">The array.</param>
        /// <param name="arrayIndex">Index of the array.</param>
        public void CopyTo(Service[] array, int arrayIndex)
        {
            PreCondition.AssertNotNull(array, "array");
            PreCondition.AssertInRange(-1, array.Length, arrayIndex, "arrayIndex");

            foreach (Service element in this)
            {
                array.SetValue(element, arrayIndex);

                if (++arrayIndex >= array.Length)
                    break;
            }
        }

        /// <summary>
        /// Removes the specified item from the current collection.
        /// </summary>
        /// <param name="item">The item to remove.</param>
        /// <returns></returns>
        public bool Remove(Service item)
        {
            return _services.Remove(item.ServiceName);
        }

        /// <summary>
        /// Refreshes the data with the underlying data source.
        /// </summary>
        public void Reset()
        {
            _services = GetServices();
            OnChanged(new ServiceCollectionChangedEventArgs(ServiceCollectionChangedAction.Reset, null));
        }

        private IDictionary<string, Service> GetServices()
        {
            IList<ServiceController> serviceControllers = _parent.DataProvider.GetServiceList();
            IDictionary<string, Service> services = new Dictionary<string, Service>();

            foreach (ServiceController serviceController in serviceControllers)
            {
                services[serviceController.ServiceName] = Converter.CreateService(serviceController);
            }
            return services;
        }

        private void UpdateServiceList(IList<ServiceControllerChange> changes)
        {
            foreach (ServiceControllerChange changedItem in changes)
            {
                switch (changedItem.Reason)
                {
                    case ServiceControllerChangeReason.Added:
                        PerformAddedUpdate(changedItem);
                        break;
                    case ServiceControllerChangeReason.Updated:
                        PerformUpdatedUpdate(changedItem);
                        break;
                    case ServiceControllerChangeReason.Deleted:
                        PerformDeletedUpdate(changedItem);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
            
        }

        private void PerformAddedUpdate(ServiceControllerChange changedItem)
        {
            if (!_services.ContainsKey(changedItem.Service.ServiceName))
            {
                Service addedService = Converter.CreateService(changedItem.Service);
                _services[changedItem.Service.ServiceName] = addedService;
                OnChanged(new ServiceCollectionChangedEventArgs(ServiceCollectionChangedAction.Add, addedService));
            }
            else
            {
                PerformUpdatedUpdate(changedItem);
            }
        }

        private void PerformUpdatedUpdate(ServiceControllerChange changedItem)
        {
            if (_services.ContainsKey(changedItem.Service.ServiceName))
            {
                _services[changedItem.Service.ServiceName].CopyValuesFrom(Converter.CreateService(changedItem.Service));
            }
            else
            {
                PerformAddedUpdate(changedItem);
            }
        }

        private void PerformDeletedUpdate(ServiceControllerChange changedItem)
        {
            if (_services.ContainsKey(changedItem.Service.ServiceName))
            {
                Service deletedService = _services[changedItem.Service.ServiceName];
                _services.Remove(changedItem.Service.ServiceName);
                OnChanged(new ServiceCollectionChangedEventArgs(ServiceCollectionChangedAction.Remove, deletedService));
            }
        }


        private void EnsureEventAdded()
        {
            if (!_eventAdded)
            {
                _parent.DataProvider.Update += OnUpdate;
                _eventAdded = true;
            }
        }

        private void EnsureEventRemoved()
        {
            if (_onCollectionChanged != null && _eventAdded)
            {
                _parent.DataProvider.Update -= OnUpdate;
                _eventAdded = false;
            }
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------
        
        /// <summary>
        /// Occurs when the collection changes.
        /// </summary>
        public event EventHandler<ServiceCollectionChangedEventArgs> CollectionChanged
        {
            add
            {
                _onCollectionChanged.Add(value);
                EnsureEventAdded();
            }
            remove
            {
                _onCollectionChanged.Remove(value);
                EnsureEventRemoved();
            }
        }

        /// <summary>
        /// Fires the Changed event.
        /// </summary>
        /// <param name="args">Arguments to pass to the event receiver.</param>
        protected virtual void OnChanged(ServiceCollectionChangedEventArgs args)
        {
            _onCollectionChanged.Raise(this, args);
        }

        private void OnUpdate(object sender, ClientDataEventArgs e)
        {
            if (_services == null)
                return;

            UpdateServiceList(e.Changes);
        }

        #endregion
    }
}
